#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/completion.h>

#define MINOR_DEV_COUNT 1

static int comp_major = 0;
static int comp_minor = 0;
static struct cdev *my_cdev = NULL;

DECLARE_COMPLETION(my_completion);

int comp_open (struct inode *node, struct file *file)
{
    printk("%s: proc %d\n", __FUNCTION__, current->pid);
    return 0;
}

int comp_release (struct inode *node, struct file *file)
{
    printk("%s: proc %d\n", __FUNCTION__, current->pid);
    return 0;
}

ssize_t comp_read (struct file *file,
                   char __user *str,
                   size_t len,
                   loff_t *off)
{
    printk("%s: process %d is going to sleep\n", __FUNCTION__, current->pid);
    wait_for_completion(&my_completion);
    printk("%s: process %d is awoken\n", __FUNCTION__, current->pid);
    
    return 0;
}

ssize_t comp_write (struct file *file,
                    const char __user *str,
                    size_t len,
                    loff_t *off)
{
    complete(&my_completion);
    printk("%s: process %d is going to awoke the reader\n", __FUNCTION__, current->pid);
    
    return len;
}

static struct file_operations flop = {
    .owner   = THIS_MODULE,
    .open    = comp_open,
    .release = comp_release,
    .read    = comp_read,
    .write   = comp_write,
};

static int __init this_init (void)
{
    int result = 0;
    dev_t dev = 0;

    result = alloc_chrdev_region(&dev, 0, MINOR_DEV_COUNT, "hello_completion");
    if (result != 0) {
        printk(KERN_DEBUG "%s:%d alloc_chrdev_region fail\n", __FUNCTION__, __LINE__);
        return result;
    }
    comp_major = MAJOR(dev);

    printk(KERN_DEBUG "dev=%d major=%d minor=%d\n", dev, comp_major, comp_minor);
    my_cdev = cdev_alloc();
    if (!my_cdev) {
        printk("%s: cdev_alloc failed\n", __FUNCTION__);
        result = -ENOMEM;
        goto FAIL;
    }

    cdev_init(my_cdev, &flop);
    cdev_add(my_cdev, dev, MINOR_DEV_COUNT);

    printk(KERN_DEBUG "%s:%d success\n", __FUNCTION__, __LINE__);
    return 0;
    
FAIL:
    unregister_chrdev_region(dev, MINOR_DEV_COUNT);
    printk(KERN_DEBUG "%s:%d, init fail, result=%d\n", __FUNCTION__, __LINE__, result);
    return result;
}

static void __exit this_exit (void)
{
    dev_t dev;
    dev = MKDEV(comp_major, comp_minor);

    cdev_del(my_cdev);
    unregister_chrdev_region(dev, MINOR_DEV_COUNT);
    printk(KERN_DEBUG "%s:%d success dev=%d\n", __FUNCTION__, __LINE__, dev);
}

module_init(this_init);
module_exit(this_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("chzhou");

